///\file

#define _POSIX_C_SOURCE 200112L

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <time.h>
#include <sys/time.h>

/**
 * \mainpage
 *
 * \authors João Donato da Silva Petroni
 * \authors Lucas Alexandre Mello Magalhaes
 * \authors Marcelo Yukio Bressan Hossomi
 *
 * Este trabalho tem como objetivos fazer a ordenação de um vetor distribuidamente e coletar
 * informações sobre o impacto da conexão entre hosts no tempo total de execução.
 *
 * Informações para o uso:
 * =======================
 * Para compilar:
 * --------------
 *  - make:\n
 *	   Compila o código fonte "node.c" gerando o executável "sockets".
 *  - make install:\n
 *     Copia o executável para todos os nós do cluster.
 *  - make doc:\n
 *     Gera a documentação a partir do doxygen em html e pdf.
 *  - make clean:\n
 *     Apaga os executáveis e a documentação.
 *
 * Para executar:
 * --------------
 *  - exec.sh:\n
 *     Executa o programa em todos os nós do cluster com os modos do programa já configurados.
 *  - exec2.sh:\n
 *     Executa o programa em localhost com os modos do programa já configurados.
 *  - Pelo executável:
 *    - sockets -c hostname porta\n
 *      Executa o programa em modo cliente, isto é, o nível mais inferior. Em que o "hostname"
 *      é o nome do host em que ele irá se conectar e a "porta" é o número da porta em que o host
 *      irá receber a conexão.
 *    - sockets -s porta\n
 *      Executa o programa em modo servidor, isto é, o nível mais superior. Em que a "porta" é
 *      o número da porta em que o servidor ficará ouvindo.
 *    - sockets -i hostname porta1 porta2\n
 *      Executa o programa em modo de nó intermediário. Em que o "hostname" e a "porta1" se
 *		assemelham aos parâmetros do cliente e a "porta2" aos do servidor.
 */


/**
 * \brief Tamanho inicial do vetor.
 *
 * Originalmente estipulado em 125. Entretanto, com este valor, os tempos obtidos variam excessivamente
 * entre uma execução e outra. Logo, para que se haja uma variação de tempo menos drástica entre as
 * execuções, um valor maior foi escolhido.
 */
#define INITIAL_LEN		1000000

void client_handler(int connection_socket);                         ///< Manipulador dos clientes.
void server_handler(int listen_socket);                             ///< Manipulador do servidor do último nível.
void node_handler(int connection_up,int listen_socket);             ///< Manipulador dos nós intermediários.
void socket_read(int fd,void* data,size_t size);                    ///< Recebe uma mensagem pelo socket, tratando os erros.
void socket_write(int fd,void* data,ssize_t size);                  ///< Envia uma mensagem pelo socket, tratando os erros.
void socket_connect(int fd,struct sockaddr* addr,socklen_t len);    ///< Realiza a conexão, tratando os erros.
void socket_bind(int fd,struct sockaddr* addr,socklen_t len);       ///< Realiza o bind, tratando os erros.
void socket_listen(int fd, int n);                                  ///< Habilita o socket a receber conexões, tratando os erros.
void print_vector(int* vector,size_t len);                          ///< Exibe o vetor na saída padrão.
int* random_vector(size_t len);                                     ///< Gera um vetor aleatório.
int  compare(const void* a, const void* b);                         ///< Função de comparação para o qsort.

/**
 * \brief Estrutura para um timer.
 *
 * Faz a medição de um intervalo de tempo.
 * Obs:	O timer não leva em consideração o tempo em que o processo ficou bloqueado ou na fila do escalonador.
 */
typedef struct{
	struct timespec delta;  ///< Armazena o intervalo total em que o timer ficou rodando.
	struct timespec t1;     ///< Marca o começo de um intervalo de tempo.
	struct timespec t2;     ///< Marca o final de um intervalo de tempo.
}sock_timer_t;

void socket_timer_start(sock_timer_t* tp);      ///< Dispara o timer. \memberof sock_timer_t
void socket_timer_pause(sock_timer_t* tp);      ///< Pausa o timer. \memberof sock_timer_t
void socket_timer_unpause(sock_timer_t* tp);    ///< Continua o timer. \memberof sock_timer_t
void socket_timer_print(sock_timer_t tp);       ///< Exibe o tempo na saída padrão. \memberof sock_timer_t

void socket_time_delta(struct timespec* tp,struct timespec tp2, struct timespec tp1);  ///< Calcula o intervalo de tempo. \memberof sock_timer_t

/**
 * \brief Faz a chamada do manipulador correto de acordo com os parâmetros do programa.
 *
 * Recebe como parâmetro o modo de execução (cliente "-c", nó intermediário "-i" e servidor "-s"), o nome do host e a
 * porta que se deve conectar e/ou a porta que se vai ouvir. Inicializa os sockets e os repassa aos devidos manipuladores.
 *
 * \param argc Número de argumentos, mínimo 3.
 * \param argv Valor dos argumentos: modo de execução, host e portas.
 * \return Na finalização correta do programa: EXIT_SUCCESS. Caso contrário: EXIT_FAILURE.
 */
int main(int argc, char **argv){
	int listen_socket, connection_socket;
	struct sockaddr_in server, client;
	struct hostent *host;
	if(argc < 3 || argv[1][0] != '-'){
		printf("Formato incorreto: [-c hostname porta] [-s porta] [-i hostname porta1 porta2] \n");
		return EXIT_FAILURE;
	}
	switch(argv[1][1]){
	case 'c':
		//Cliente
		if(argc < 4){
			printf("Formato incorreto: -c [hostname porta] \n");
			return EXIT_FAILURE;
		}
		connection_socket = socket(AF_INET, SOCK_STREAM, 0);

		memset(&client,0,sizeof(client));
		host = gethostbyname(argv[2]);
		if(host == NULL){
			perror("Host name.");
			abort();
		}
		client.sin_family = AF_INET;
		client.sin_addr = *(struct in_addr*)host->h_addr_list[0];
		client.sin_port = htons(atoi(argv[3]));

		socket_connect(connection_socket,(struct sockaddr*)&client,sizeof(client));
		printf("Connected.\n");

		client_handler(connection_socket);

		close(connection_socket);

		break;
	case 's':
		//Server
		listen_socket = socket(AF_INET,SOCK_STREAM,0);

		memset(&server,0,sizeof(server));
		server.sin_family = AF_INET;
		server.sin_addr.s_addr = INADDR_ANY;
		server.sin_port = htons(atoi(argv[2]));

		socket_bind(listen_socket,(struct sockaddr*)&server,sizeof(server));
		socket_listen(listen_socket,10);
		printf("Listen.\n");

		server_handler(listen_socket);

		close(listen_socket);

		break;
	case 'i':
		//Níveis intermediários
		if(argc < 5){
			printf("Formato incorreto: -i [hostname porta1 porta2] \n");
			return EXIT_FAILURE;
		}
		listen_socket = socket(AF_INET,SOCK_STREAM,0);
		connection_socket = socket(AF_INET, SOCK_STREAM, 0);

		memset(&server,0,sizeof(server));
		server.sin_family = AF_INET;
		server.sin_addr.s_addr = INADDR_ANY;
		server.sin_port = htons(atoi(argv[4]));

		memset(&client,0,sizeof(client));
		host = gethostbyname(argv[2]);
		if(host == NULL){
			perror("Host name.");
			abort();
		}
		client.sin_family = AF_INET;
		client.sin_addr = *(struct in_addr*)host->h_addr_list[0];
		client.sin_port = htons(atoi(argv[3]));

		socket_bind(listen_socket,(struct sockaddr*)&server,sizeof(server));
		socket_listen(listen_socket,10);
		printf("Listen.\n");

		socket_connect(connection_socket,(struct sockaddr*)&client,sizeof(client));
		printf("Connected.\n");

		node_handler(connection_socket,listen_socket);

		close(connection_socket);
		close(listen_socket);

		break;
	default:
		printf("Formato incorreto: [-c hostname porta] [-s porta] [-i hostname porta1 porta2] \n");
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

/**
 * \defgroup handlers Funções manipuladoras
 * Funções encarregadas de gerenciar cada um dos modos de execução do programa: cliente, nós intermediários e servidor.
 * @{
 */

/**
 * Gera um vetor inicial aleatório, o ordena, envia para o nível superior
 * e aguarda o recebimento do vetor completo e ordenado.
 *
 * \param connection_socket File descriptor do socket que se conecta ao nível superior.
 */
void client_handler(int connection_socket){
	int* vector;
	size_t len = INITIAL_LEN;
	sock_timer_t t_total;       //Tempo total de execução.
	sock_timer_t t_sort;        //Tempo de ordenação do vetor.
	sock_timer_t t_connection;  //Tempo realizando o envio/recebimento de mensagens.
	sock_timer_t t_io;			//Tempo realizando a exibição do vetor.
	sock_timer_t t_pvec;		//Tempo de inicialização do vetor.

	//Inicialização do vetor.
	socket_timer_start(&t_total);
	socket_timer_start(&t_pvec);
	vector = random_vector(len);
	socket_timer_pause(&t_pvec);

	printf("Initial Vector:\n");
	socket_timer_start(&t_io);
	print_vector(vector,len);
	socket_timer_pause(&t_io);

	//Ordena o vetor.
	socket_timer_start(&t_sort);
	qsort(vector,len,sizeof(int),compare);
	socket_timer_pause(&t_sort);

	//Envio ao nível superior.
	socket_timer_start(&t_connection);
	socket_write(connection_socket,&len,sizeof(len));
	socket_write(connection_socket,vector,len*sizeof(int));
	socket_timer_pause(&t_connection);
	printf("Vector sent.\n");
	free(vector);

	//Recebimento do vetor ordenado.
	socket_timer_unpause(&t_connection);
	socket_read(connection_socket,&len,sizeof(len));
	socket_timer_pause(&t_connection);
	vector = malloc(len*sizeof(int));
	socket_timer_unpause(&t_connection);
	socket_read(connection_socket,vector,len*sizeof(int));
	socket_timer_pause(&t_connection);

	printf("Ordered vector received.\n");

	close(connection_socket);

	//Exibição dos resultados.
	printf("Vector:\n");
	socket_timer_unpause(&t_io);
	print_vector(vector,len);
	socket_timer_pause(&t_io);
	free(vector);
	socket_timer_pause(&t_total);
	printf("Populate vector time: ");
	socket_timer_print(t_pvec);
	printf("Sort time:            ");
	socket_timer_print(t_sort);
	printf("I/O time:             ");
	socket_timer_print(t_io);
	printf("Connection time:      ");
	socket_timer_print(t_connection);
	printf("Total Time:           ");
	socket_timer_print(t_total);
}

/**
 * Aguarda o recebimento dos vetores, faz a ordenação final e envia o
 * vetor completo e ordenado ao nível inferior.
 *
 * \param listen_socket File descritor do socket que ouve o nível inferior.
 */
void server_handler(int listen_socket){
	int num_connections = 2;
	int connection_socket[num_connections];
	size_t len;
	int* vector;
	int loop;
	sock_timer_t t_total;             //Tempo total de execução.
	sock_timer_t t_sort;              //Tempo de ordenação do vetor.
	sock_timer_t t_connection;        //Tempo realizando o envio/recebimento de mensagens.
	sock_timer_t t_io;                //Tempo realizando a exibição do vetor.

	socket_timer_start(&t_total);

	//Aceita as conexões do nível inferior e armazena os vetores recebidos.
	for(loop = 0; loop < num_connections; loop++){
		connection_socket[loop] = accept(listen_socket,NULL,NULL);
		printf("Connection accepted.\n");

		if(loop == 0)
			socket_timer_start(&t_connection);
		else
			socket_timer_unpause(&t_connection);

		socket_read(connection_socket[loop],&len,sizeof(len));
		socket_timer_pause(&t_connection);

		if(loop == 0)
			vector = malloc(num_connections*len*sizeof(int));

		socket_timer_unpause(&t_connection);
		socket_read(connection_socket[loop],vector+loop*len,len*sizeof(int));
		socket_timer_pause(&t_connection);
		printf("Vector received.\n");
	}

	//Exibe o vetor desordenado.
	len *= num_connections;
	printf("len: %zu\n",len);
	socket_timer_start(&t_io);
	print_vector(vector,len);
	socket_timer_pause(&t_io);

	//Ordena o vetor.
	socket_timer_start(&t_sort);
	qsort(vector,len,sizeof(int),compare);
	socket_timer_pause(&t_sort);

	//Envia o vetor já ordenado ao nível inferior..
	for(loop = 0; loop < num_connections; loop++){
		socket_timer_unpause(&t_connection);
		socket_write(connection_socket[loop],&len,sizeof(len));
		socket_write(connection_socket[loop],vector,len*sizeof(int));
		socket_timer_pause(&t_connection);
		close(connection_socket[loop]);
	}
	printf("Vector sent.\n");
	free(vector);

	//Exibição dos resultados.
	socket_timer_pause(&t_total);
	printf("Sort time:       ");
	socket_timer_print(t_sort);
	printf("I/O time:        ");
	socket_timer_print(t_io);
	printf("Connection time: ");
	socket_timer_print(t_connection);
	printf("Total time:      ");
	socket_timer_print(t_total);
}

/**
 * Aguarda o recebimento do vetor do nível inferior, faz a ordenação e repassa o vetor
 * resultante ao nível superior. Aguarda o retorno do vetor completo do nível superior
 * e o repassa ao nível inferior.
 *
 * \param connection_up File descriptor do socket que se conecta no nível superior.
 * \param listen_socket File descriptor do socket que ouve o nível inferior.
 */
void node_handler(int connection_up, int listen_socket){
	int num_connections = 2;
	int connection_socket[num_connections];
	size_t len;
	int* vector;
	int loop;
	sock_timer_t t_total;       //Tempo total de execução.
	sock_timer_t t_sort;        //Tempo de ordenação do vetor.
	sock_timer_t t_connection;  //Tempo realizando o envio/recebimento de mensagens.
	sock_timer_t t_io;          //Tempo realizando a exibição do vetor.

	socket_timer_start(&t_total);

	//Aceita as conexões do nível inferior e armazena os vetores recebidos.
	for(loop = 0; loop < num_connections; loop++){
		connection_socket[loop] = accept(listen_socket,NULL,NULL);
		printf("Connection accepted.\n");

		if(loop == 0)
			socket_timer_start(&t_connection);
		else
			socket_timer_unpause(&t_connection);

		socket_read(connection_socket[loop],&len,sizeof(len));
		socket_timer_pause(&t_connection);

		if(loop == 0)
			vector = malloc(num_connections*len*sizeof(int));

		socket_timer_unpause(&t_connection);
		socket_read(connection_socket[loop],vector+loop*len,len*sizeof(int));
		socket_timer_pause(&t_connection);
		printf("Vector received.\n");
	}

	//Exibe o vetor desordenado.
	len *= num_connections;
	printf("len: %zu\n",len);
	socket_timer_start(&t_io);
	print_vector(vector,len);
	socket_timer_pause(&t_io);

	//Ordena o vetor.
	socket_timer_start(&t_sort);
	qsort(vector,len,sizeof(int),compare);
	socket_timer_pause(&t_sort);

	//Envia o vetor já ordenado ao nível superior.
	socket_timer_unpause(&t_connection);
	socket_write(connection_up,&len,sizeof(len));
	socket_write(connection_up,vector,len*sizeof(int));
	socket_timer_pause(&t_connection);
	printf("Vector send.\n");
	free(vector);

	//Recebe o vetor ordenado do nível superior.
	socket_timer_unpause(&t_connection);
	socket_read(connection_up,&len,sizeof(len));
	socket_timer_pause(&t_connection);
	vector = malloc(len*sizeof(int));
	socket_timer_unpause(&t_connection);
	socket_read(connection_up,vector,len*sizeof(int));
	socket_timer_pause(&t_connection);
	printf("Ordered vector received.\n");

	//Repassa o vetor recebido ao nível inferior.
	for(loop = 0; loop < num_connections; loop++){
		socket_timer_unpause(&t_connection);
		socket_write(connection_socket[loop],&len,sizeof(len));
		socket_write(connection_socket[loop],vector,len*sizeof(int));
		socket_timer_pause(&t_connection);
		close(connection_socket[loop]);
	}
	printf("Ordered vector sent.\n");
	free(vector);

	//Exibição dos resultados.
	socket_timer_pause(&t_total);
	printf("Sort time:       ");
	socket_timer_print(t_sort);
	printf("I/O time:        ");
	socket_timer_print(t_io);
	printf("Connection time: ");
	socket_timer_print(t_connection);
	printf("Total time:      ");
	socket_timer_print(t_total);
}

/**
 * @}
 */

/**
 * \defgroup sockets Funções de tratamento de sockets
 * Funções encarregadas de realizar as operações nos sockets e tratar os eventuais erros.
 * @{
 */

/**
 * Recebe uma mensagem pelo socket "fd" via função "read", caso haja algum erro ele é mostrado na stderr e o programa é abortado.
 *
 * \param fd File descriptor do socket.
 * \param data Ponteiro para a região de memória em que o dado será guardado.
 * \param size Tamanho do dado que se recebe.
 */
void socket_read(int fd,void* data,size_t size){
	ssize_t ret;
	while((ret = read(fd,data,size)) < size){
		if(ret < 0){
			//Em caso de erro:
			perror("Read Error");
			abort();
		}else{
			//Caso a informação não tenha sido enviada completamente:
			data = ((uint8_t*)data) + ret;
			size -= ret;
		}
	}
}

/**
 * Envia uma mensagem pelo socket "fd" via função "write", caso haja algum erro ele é mostrado na stderr e o programa é abortado.
 *
 * \param fd File descriptor do socket.
 * \param data Ponteiro para a região de memória do dado será enviado.
 * \param size Tamanho do dado que se envia.
 */
void socket_write(int fd,void* data,ssize_t size){
	ssize_t ret;
	while((ret = write(fd,data,size)) < size){
		if(ret < 0){
			//Em caso de erro:
			perror("Write Error");
			abort();
		}else{
			//Caso a informação não tenha sido recebida completamente:
			data = ((uint8_t*)data) + ret;
			size -= ret;
		}
	}
}

/**
 * Conecta o socket "fd" a "addr", caso haja algum erro ele é mostrado na stderr e o programa é abortado.
 *
 * \param fd File descriptor do socket de conexão.
 * \param addr Endereço do socket a se conectar.
 * \param len Tamanho do socket.
 */
void socket_connect(int fd,struct sockaddr* addr,socklen_t len){
	if(connect(fd,addr,len) < 0){
		perror("Connection Error");
		abort();
	}
}

/**
 * Associa o socket "fd" a "addr", caso haja algum erro ele é mostrado na stderr e o programa é abortado.
 *
 * \param fd File descriptor do socket.
 * \param addr Endereço a associar com o socket.
 * \param len Tamanho do socket.
 */
void socket_bind(int fd,struct sockaddr* addr,socklen_t len){
	if(bind(fd,addr,len) < 0){
		perror("Bind Error");
		abort();
	}
}

/**
 * Habilita o socket "fd" a receber "n" conexões, caso haja algum erro ele é mostrado na stderr e o programa é abortado.
 *
 * \param fd File descriptor do socket.
 * \param n Número máximo de conexões pendentes.
 */
void socket_listen(int fd, int n){
	if(listen(fd,n) < 0){
		perror("Listen Error");
		abort();
	}
}

/**
 * @}
 */


/**
 * \param vector Vetor que será exibido.
 * \param len Número total de posições do vetor.
 */
void print_vector(int* vector,size_t len){
	size_t loop;
	for(loop = 0; loop < len; loop++){
		if(loop%10 == 0 && loop != 0)
			printf("\n%3d ",vector[loop]);
		else
			printf("%3d ",vector[loop]);
	}
	printf("\n");
}

/**
 * Aloca e inicializa um vetor com valores aleatórios utilizando o /dev/urandom.
 *
 * \param len Número total de posições do vetor a ser gerado.
 * \return Vetor alocado e inicializado com valores randômicos.
 */
int* random_vector(size_t len){
	int* tmp;
	size_t loop;
	int fd;
	fd = open("/dev/urandom",O_RDONLY);
	tmp = malloc(len*sizeof(int));
	for(loop = 0; loop < len; loop++){
		read(fd ,tmp+loop,sizeof(int));
		tmp[loop] = abs(tmp[loop] % 1000);
	}
	close(fd);
	return tmp;
}

/**
 * Realiza uma subtração entre os dois elementos do vetor que se quer comparar.
 *
 * \param a Ponteiro para o primeiro elemento.
 * \param b Ponteiro para o segundo elemento.
 * \return 0 se igual, maior que 0 se a > b, menor que 0 se b < a.
 */
int compare(const void* a, const void* b){
	return *(const int*)a - *(const int*)b;
}

/**
 * \memberof sock_timer_t
 * \param tp O timer a ser disparado.
 */
void socket_timer_start(sock_timer_t* tp){
	tp->delta.tv_sec = 0;
	tp->delta.tv_nsec = 0;
	tp->t1.tv_nsec = 0;
	tp->t1.tv_nsec = 0;
	tp->t2.tv_nsec = 0;
	tp->t2.tv_nsec = 0;
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&tp->t1);
}

/**
 * \memberof sock_timer_t
 * \param tp O timer a ser pausado.
 */
void socket_timer_pause(sock_timer_t* tp){
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&tp->t2);
	socket_time_delta(&tp->delta,tp->t2,tp->t1);
}

/**
 * \memberof sock_timer_t
 * \param tp O timer a ser continuado.
 */
void socket_timer_unpause(sock_timer_t* tp){
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&tp->t1);
}

/**
 * \memberof sock_timer_t
 * \param tp O timer a ser exibido.
 */
void socket_timer_print(sock_timer_t tp){
	printf("sec: %03ld nanosec: %09ld\n",tp.delta.tv_sec,tp.delta.tv_nsec);
}

/**
 * \memberof sock_timer_t
 *
 * Calcula o período do intervalo entre "tp2" e "tp1" e o adiciona em "tp".
 *
 * \param tp Resultado final: tp += tp2 - tp1.
 * \param tp2 Tempo 2.
 * \param tp1 Tempo 1.
 */
void socket_time_delta(struct timespec* tp,struct timespec tp2, struct timespec tp1){
	struct timespec temp;
	temp.tv_sec = tp2.tv_sec - tp1.tv_sec;
	if(tp2.tv_nsec >= tp1.tv_nsec)
		temp.tv_nsec = tp2.tv_nsec - tp1.tv_nsec;
	else{
		temp.tv_nsec = 1E+9 + tp2.tv_nsec - tp1.tv_nsec;
		temp.tv_sec--;
	}
	tp->tv_sec += temp.tv_sec;
	tp->tv_nsec += temp.tv_nsec;
	if(tp->tv_nsec >= 1E+9){
		tp->tv_sec++;
		tp->tv_nsec -= 1E+9;
	}
}
